Skip to content

feat(codec): add compression API, standard algorithms, and global reg…#2648

Open
sauravzg wants to merge 1 commit into
masterfrom
sauravz/compression-api
Open

feat(codec): add compression API, standard algorithms, and global reg…#2648
sauravzg wants to merge 1 commit into
masterfrom
sauravz/compression-api

Conversation

@sauravzg

Copy link
Copy Markdown
Contributor

…istry

This change introduces the foundational compression abstractions and implementations for the grpc server component.

  • Compression API (grpc/src/codec/compression.rs): Defines the Compressor and Decompressor traits for handling the compression and decompression of gRPC payloads via byte buffers.
  • Simplified Design Philosophy: The API is intentionally kept simple. It deliberately avoids handling concerns like limiting input and output buffer sizes, instead delegating these safety boundaries to the higher-level gRPC business logic.
  • Performance Improvements: By relying on direct buffer manipulation, this implementation avoids the hidden memory copying overhead present in the tonic crate (which utilizes 8KB intermediate buffers for I/O).
  • Standard Algorithms: Implements gzip, deflate, and zstd compression logic in their respective modules. These are conditionally compiled based on feature flags.
  • Global Registry (registry.rs): Introduces a thread-safe GlobalCompressionRegistry (backed by RwLock and Arc) to manage registered compressors and decompressors. It handles safe concurrent access and automatically updates the broadcasted grpc-accept-encoding headers.
  • Module Integration: Exposes the new codec and compression modules to the library's root structure.

@sauravzg sauravzg requested a review from dfawley May 19, 2026 14:20
@dfawley

dfawley commented May 21, 2026

Copy link
Copy Markdown
Member

Standard Algorithms: Implements gzip, deflate, and zstd compression logic in their respective modules. These are conditionally compiled based on feature flags.

gRPC-Go only implements gzip. Do any other languages offer these other ones out of the box?

@dfawley dfawley left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please make sure all files include the copyright header.

Comment thread grpc/src/codec/compression/gzip.rs
use crate::codec::compression::Decompressor;

/// A read-only interface to query supported compression algorithms.
pub trait CompressionResolver: Send + Sync + 'static {

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What problem is solved by putting traits around the registry?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The traits serve two main purposes:

Testing: Global singletons are painful to test with Rust's parallel test runner. An interface allows for easy mocks or fakes in unit tests.

Interface Segregation/data hiding: Interceptors only need 'get' APIs, so I needed a trait for read-only things. I could've probably achieved this via making the write methods &mut self , but it seems like for global registries interior mutability was more common (tracing lib).

Although, I must admit it's mostly the former, global singletons are painful during testing.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

But this is just a glorified map -- surely we don't need a mock implementation of the map, do we? Whether we have a global instance of it or one that gets passed around is independent from whether we have a trait for it. The trait here feels like overkill.

Comment thread grpc/src/lib.rs Outdated
Comment thread grpc/src/codec/compression.rs Outdated
@sauravzg sauravzg force-pushed the sauravz/compression-api branch from db718f9 to 2e65c81 Compare May 22, 2026 11:13
…istry

This change introduces the foundational compression abstractions and
implementations for the grpc server component.

- **Compression API (`grpc/src/codec/compression.rs`)**: Defines the
  `Compressor` and `Decompressor` traits for handling the compression
  and decompression of gRPC payloads via byte buffers.
- **Simplified Design Philosophy**: The API is intentionally kept
  simple. It deliberately avoids handling concerns like limiting input
  and output buffer sizes, instead delegating these safety boundaries to
  the higher-level gRPC business logic.
- **Performance Improvements**: By relying on direct buffer
  manipulation, this implementation avoids the hidden memory copying
  overhead present in the `tonic` crate (which utilizes 8KB intermediate
  buffers for I/O).
- **Standard Algorithms**: Implements `gzip`, `deflate`, and `zstd`
  compression logic in their respective modules. These are conditionally
  compiled based on feature flags.
- **Global Registry (`registry.rs`)**: Introduces a thread-safe
  `GlobalCompressionRegistry` (backed by `RwLock` and `Arc`) to manage
  registered compressors and decompressors. It handles safe concurrent
  access and automatically updates the broadcasted
  `grpc-accept-encoding` headers.
- **Module Integration**: Exposes the new `codec` and `compression`
  modules to the library's root structure.
use crate::codec::compression::Decompressor;

/// A read-only interface to query supported compression algorithms.
pub trait CompressionResolver: Send + Sync + 'static {

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

But this is just a glorified map -- surely we don't need a mock implementation of the map, do we? Whether we have a global instance of it or one that gets passed around is independent from whether we have a trait for it. The trait here feels like overkill.

/// Returns an `io::Error` if compression fails. Implementations should gracefully
/// handle constrained `destination` buffers by returning an error rather than panicking
/// (e.g., by verifying `destination.remaining_mut()` is sufficient before writing).
fn compress(&self, source: &mut dyn Buf, destination: &mut dyn BufMut) -> Result<(), String>;

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

SendMessage has the return type of Result<Box<dyn Buf + Send + Sync>, String>. I believe these two traits should match (one way or the other). Is there any advantage to having gRPC allocate the destination BufMut as opposed to letting the codec produce the Buf? The latter seems more flexible than the former depending on the implementation of the codec itself -- it seems like if the compression library's API isn't as we expect, then you could end up having to do a byte-wise copy to get it into the BufMut we allocate (put<T:Buf> does a copy).

I believe if we return a Buf that we could have gzip allocate a BytesMut and use that with the gzip library (it impls BufMut) and return it (it also impls Buf).

/// Returns an `io::Error` if decompression fails. Implementations should gracefully
/// handle constrained `destination` buffers by returning an error rather than panicking
/// (e.g., by verifying `destination.remaining_mut()` is sufficient before writing).
fn decompress(&self, source: &mut dyn Buf, destination: &mut dyn BufMut) -> Result<(), String>;

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

(The same decision would apply here I believe.)

Comment on lines +32 to +33
#[cfg(feature = "zstd")]
mod zstd;

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It seems grpc-C++ supports deflate? But I don't think anything supports zstd:

https://github.com/grpc/grpc/blob/dc5bf65c0eae98dd47cf0ba4a8f2468b7708958b/include/grpc/impl/compression_types.h#L63

It might even be best to remove deflate for now unless we have an interop test to confirm they work together.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants